/* 
 *	Copyright 2014  Yanqing Wu
 *		email: yqwu_yqwu@126.com
 *
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
*/

#include "stdafx.h"
#include "StdCtrlSkinTable.h"

#include "StdCtrlSkinTool_Utils.h"
#include "StdCtrlSkinWnd.h"
#include "StdCtrlSkinBg.h"

void CStdCtrlSkinTable::InitDataElt(CStdCtrlSkinWnd* pWnd, CStdCtrlSkinBg* pBg)
{
	m_weight = 0;
	m_expand = (CStdCtrlSkin_EnumExpand)0;
	m_rows = 0;
	m_cols = 0;
	m_totalweight_col = 0;
	m_totalweight_row = 0;
	m_padx = 0;
	m_pady = 0;

	m_pWnd = pWnd;
	m_pBg = pBg;

	if ( m_pBg )
		m_layout_table = false;
	else
		m_layout_table = true;
}

CStdCtrlSkinTable::CStdCtrlSkinTable(CStdCtrlSkinWnd* pWnd)
{
	InitDataElt(pWnd, nullptr);
}
CStdCtrlSkinTable::CStdCtrlSkinTable(CComPtr<IXMLDOMNode> spNode, CStdCtrlSkinWnd* pWnd)
{
	InitDataElt(pWnd, nullptr);
	LoadXmlNode(spNode);
}
CStdCtrlSkinTable::CStdCtrlSkinTable(CComPtr<IXMLDOMNode> spNode, CStdCtrlSkinBg* pBg)
{
	InitDataElt(nullptr, pBg);
	LoadXmlNode(spNode);
}

CStdCtrlSkinTable::~CStdCtrlSkinTable(void)
{
	for(auto i : m_row_list)
		delete i.second;
}

CString CStdCtrlSkinTable::GetFilePath(void)
{
	if ( m_pWnd )
		return m_pWnd->GetFilePath();
	if ( m_pBg )
		return m_pBg->GetFilePath();
	return _T("");
}

bool CStdCtrlSkinTable::LoadXmlNode(CComPtr<IXMLDOMNode> spNode)
{
	auto attribPairs = StdCtrlSkinTool_XmlGetAttribPairs(spNode);
	auto pEnum = StdCtrlSkinTool_GetEnum();
	CStdCtrlSkin_EnumAttrib attrib;
	for(auto i : attribPairs)
	{
		auto name = i.first;
		auto value = i.second;
		auto correct = pEnum->Parse(attrib, name);
		if ( !correct )
		{
			SetCustomAttrib(name, value);
			continue;
		}
		switch(attrib)
		{
		case CStdCtrlSkin_EnumAttrib::PADX:
			m_padx = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::PADY:
			m_pady = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::EXPAND:
			pEnum->ParseCombo(m_expand, value);
			break;
		case CStdCtrlSkin_EnumAttrib::WEIGHT:
			m_weight = StdCtrlSkinTool_CString2Long(value);
			break;
		default:
			break;
		}
	}

	CComPtr<IXMLDOMNodeList> spChildren;
	spNode->get_childNodes(&spChildren);
	long length;
	spChildren->get_length(&length);
	CStdCtrlSkinRow* pRow;
	for(long i=0; i!=length; ++i)
	{
		CComPtr<IXMLDOMNode> spChild;
		spChildren->get_item(i, &spChild);
		auto name = StdCtrlSkinTool_XmlGetNodeName(spChild);
		CStdCtrlSkin_EnumSubNode subnode;
		pEnum->Parse(subnode, name);
		switch(subnode)
		{
		case CStdCtrlSkin_EnumSubNode::ROW:
			pRow = new CStdCtrlSkinRow(spChild, this);
			m_row_list[m_rows] = pRow;
			pRow->row = m_rows;
			m_rows ++;
			break;
		case CStdCtrlSkin_EnumSubNode::COLCONFIG:
			LoadColConfig(spChild);
			break;
		default:
			break;
		}
	}
	PostLoading();
	return true;
}

bool CStdCtrlSkinTable::LoadColConfig(CComPtr<IXMLDOMNode> spNode)
{
	auto attribPair = StdCtrlSkinTool_XmlGetAttribPairs(spNode);
	auto pEnum = StdCtrlSkinTool_GetEnum();
	CStdCtrlSkin_EnumAttrib attrib;
	UINT col = 0, weight = 0;
	CStdCtrlSkinAttrib colAttrib(false);
	for(auto i : attribPair)
	{
		auto name = i.first;
		auto value = i.second;
		auto correct = pEnum->Parse(attrib, name);
		if ( !correct )
			continue;
		switch(attrib)
		{
		case CStdCtrlSkin_EnumAttrib::COL:
			col = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::WEIGHT:
			weight = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::WIDTH:
			colAttrib.width = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::HEIGHT:
			colAttrib.height = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::PADX:
			colAttrib.padx = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::PADY:
			colAttrib.pady = StdCtrlSkinTool_CString2Long(value);
			break;
		case CStdCtrlSkin_EnumAttrib::STICKY:
			pEnum->ParseCombo(colAttrib.sticky, value);
			break;
		default:
			break;
		}
	}
	m_col_weight_list[col] = weight;
	m_col_attrib_list[col] = colAttrib;
	return true;
}

bool CStdCtrlSkinTable::PostLoading(void)
{
	std::map<UINT, std::map<LPARAM, UINT>> cell_width_array;
	std::map<UINT, std::map<LPARAM, UINT>> cell_height_array;
	std::map<UINT, UINT> rowStops, rowStarts;
	m_cols = 0;
	//	Load min width/height of each cell, so that calculate width of each
	//	column, height of each row
	for(UINT i=0; i<m_rows; i++)
	{
		rowStops[i] = 0;
		m_cols = max(m_cols, m_row_list[i]->cols);
		cell_width_array[i] = m_row_list[i]->GetCellWidthStops();
		cell_height_array[i] = m_row_list[i]->GetCellHeightStops();
	}
	//	Calculate the width of each column
	CalculateColPoints(cell_width_array);

	//	For each row, look for the biggest extent of height
	CalculateRowPoints(cell_height_array);

	//	Calculate (total) weight of each row/col
	for(auto i : m_row_list)
	{
		auto row = i.first;
		auto pRow = i.second;
		m_row_weight_list[row] = pRow->weight;
		m_totalweight_row += pRow->weight;
	}
	for(UINT i=0; i<m_cols; i++)
	{
		if ( m_col_weight_list.find(i)==m_col_weight_list.end() )
			m_col_weight_list[i] = 0;
		m_totalweight_col += m_col_weight_list[i];
	}
	return true;
}

void CStdCtrlSkinTable::CalculateColPoints(std::map<UINT, std::map<LPARAM, UINT>> extArray)
{
	m_col_point_list[0] = 0;
	for(UINT i=0; i<m_cols; i++)
	{
		m_col_point_list[i+1] = m_col_point_list[i];
		for(UINT j=0; j<m_rows; j++)
		{
			auto rowData = extArray[j];
			for(auto extData : rowData)
			{
				LPARAM key = extData.first;
				auto end = HIWORD(key);
				if ( end == i+1 )
				{
					auto start = LOWORD(key);
					auto point = m_col_point_list[start] + extData.second;
					m_col_point_list[i+1] = max(m_col_point_list[i+1], point);
				}
			}
		}
	}
}

void CStdCtrlSkinTable::CalculateRowPoints(std::map<UINT, std::map<LPARAM, UINT>> extArray)
{
	m_row_point_list[0] = 0;
	for(UINT i=0; i<m_rows; i++)
	{
		m_row_point_list[i+1] = m_row_point_list[i];
		for(UINT j=0; j<=i; j++)
		{
			auto rowData = extArray[j];
			for(auto extData : rowData)
			{
				LPARAM key = extData.first;
				auto end = HIWORD(key);
				if ( end == i+1 )
				{
					auto start = LOWORD(key);
					auto point = m_row_point_list[start] + extData.second;
					m_row_point_list[i+1] = max(m_row_point_list[i+1], point);
				}
			}
		}
	}
}

/*
 *	A table is resizable at one direction when:
 *	1.	The weight of this table is greater than 0, and
 *	2.	The table is expandable at the direction given, and
 *	3.	At least one row has weight greater than 0 when resizing at y, same as at x
*/
bool CStdCtrlSkinTable::IsResizable(CStdCtrlSkin_EnumExpand direction)
{
	if ( m_weight==0 )
		return false;
	if ( ((int)m_expand & (int)direction) == 0 )
		return false;
	switch(direction)
	{
	case CStdCtrlSkin_EnumExpand::X:
		if ( m_totalweight_col )
			return true;
		return false;
	case CStdCtrlSkin_EnumExpand::Y:
		if ( m_totalweight_row )
			return true;
		return false;
	default:
		return false;
	}
}

UINT CStdCtrlSkinTable::GetWeight(void)
{
	if ( IsResizable(CStdCtrlSkin_EnumExpand::Y) )
		return m_weight;
	return 0;
}

CSize CStdCtrlSkinTable::GetMinTableSize(void)
{
	CSize result;
	result.cx = m_col_point_list[m_cols] + m_padx * 2;
	result.cy = m_row_point_list[m_rows] + m_pady * 2;
	return result;
}
CSize CStdCtrlSkinTable::GetMinContentSize(void)
{
	CSize result;
	result.cx = m_col_point_list[m_cols];
	result.cy = m_row_point_list[m_rows];
	return result;
}

void CStdCtrlSkinTable::SetRect(CRect rect)
{
	m_rect = rect;
	m_rect.top += m_pady;
	m_rect.left += m_padx;
	m_rect.bottom -= m_pady;
	m_rect.right -= m_padx;
	auto width = m_rect.right - m_rect.left;
	auto height = m_rect.bottom - m_rect.top;
	auto minSize = GetMinContentSize();
	//	m_rect size should be the min size if table is not resizable, or the rect
	//	given is smaller than min size
	if ( width > minSize.cx )
	{
		if ( !IsResizable(CStdCtrlSkin_EnumExpand::X) )
			m_rect.right = m_rect.left + minSize.cx;
	}
	else
	{
		m_rect.right = m_rect.left + minSize.cx;
	}
	if ( height > minSize.cy )
	{
		if ( !IsResizable(CStdCtrlSkin_EnumExpand::Y) )
			m_rect.bottom = m_rect.top + minSize.cy;
	}
	else
		m_rect.bottom = m_rect.top + minSize.cy;
	CalculateShowPoints();
}

void CStdCtrlSkinTable::CalculateShowPoints(void)
{
	auto minSize = GetMinContentSize();
	auto showWidth = m_rect.right - m_rect.left, showHeight = m_rect.bottom - m_rect.top;
	double incrX=0, incrY=0;
	if ( m_totalweight_col>0 )
		incrX = 1.0 * (showWidth - minSize.cx) / m_totalweight_col;
	if ( m_totalweight_row>0 )
		incrY = 1.0 * (showHeight - minSize.cy) / m_totalweight_row;
	m_show_col_point_list[0] = m_rect.left;
	for(UINT i=0; i<m_cols; i++)
	{
		auto colWidth = m_col_point_list[i+1] - m_col_point_list[i];
		m_show_col_point_list[i+1] = m_show_col_point_list[i] + colWidth + incrX * m_col_weight_list[i];
	}
	m_show_row_point_list[0] = m_rect.top;
	for(UINT i=0; i<m_rows; i++)
	{
		auto rowHeight = m_row_point_list[i+1] - m_row_point_list[i];
		m_show_row_point_list[i+1] = m_show_row_point_list[i] + rowHeight + incrY * m_row_weight_list[i];
	}
}

int CStdCtrlSkinTable::GetRowStart(UINT row)
{
	if ( row>=m_rows )
		return m_show_row_point_list[m_rows];
	return m_show_row_point_list[row];
}
int CStdCtrlSkinTable::GetRowEnd(UINT row)
{
	if ( row>=m_rows )
		return m_show_row_point_list[m_rows];
	return m_show_row_point_list[row+1];
}

int CStdCtrlSkinTable::GetColStart(UINT col)
{
	if ( col>=m_cols )
		return m_show_col_point_list[m_cols];
	return m_show_col_point_list[col];
}
int CStdCtrlSkinTable::GetColEnd(UINT col)
{
	if ( col>=m_cols )
		return m_show_col_point_list[m_cols];
	return m_show_col_point_list[col+1];
}

//	Generate name 2 cell list
void CStdCtrlSkinTable::GenerateName2Cell(std::map<CString, CStdCtrlSkinCell*>& name2cellList)
{
	for(auto i : m_row_list)
		i.second->GenerateName2Cell(name2cellList);
}

CStdCtrlSkinAttrib CStdCtrlSkinTable::GetColAttrib(UINT col)
{
	if ( m_col_attrib_list.find(col)==m_col_attrib_list.end() )
	{
		CStdCtrlSkinAttrib result(false);
		return result;
	}
	return m_col_attrib_list[col];
}

void CStdCtrlSkinTable::Draw(HDC hDC)
{
	if ( IsLayoutTable() )
		return;
	for(auto i : m_row_list)
		i.second->Draw(hDC);
}

//	Dump the attributes and row/col size for testing
#ifdef __DUMPTEST
void CStdCtrlSkinTable::Dump(std::ofstream& fio, CStdCtrlSkin_EnumDump type)
{
	//	Dump general info, including expand, weight, padx, pady
	auto pEnum = StdCtrlSkinTool_GetEnum();
	auto expandStr = pEnum->ComboToString(m_expand);
	CStringA expandStrA(expandStr);
	fio << "Table info\n";
	fio << "\texpand=" << expandStrA.GetString() << " weight=" << m_weight
		<< " padx=" << m_padx << " pady=" << m_pady << std::endl;
	DumpRect(fio, type);
	CStdCtrlSkin_BaseElt::Dump(fio, type);
	//	Dump row info, including total weight, height/weight of each row
	DumpMinGrid(fio, type);
	DumpShowGrid(fio, type);
	//	Dump rows
	for(auto i : m_row_list)
		i.second->Dump(fio, type);
}

void CStdCtrlSkinTable::DumpRect(std::ofstream& fio, CStdCtrlSkin_EnumDump type)
{
	if ( !((int)type & (int)CStdCtrlSkin_EnumDump::TABLE_RECT) )
		return;
	fio << "Table rect=(" << m_rect.left << "," << m_rect.top << ")-("
		<< m_rect.right << "," << m_rect.bottom << ")\n";
}

void CStdCtrlSkinTable::DumpMinGrid(std::ofstream& fio, CStdCtrlSkin_EnumDump type)
{
	if ( !((int)type & (int)CStdCtrlSkin_EnumDump::TABLE_MIN_GRID) )
		return;
	fio << "Min grid info\n";
	fio << "  rows\n";
	fio << "\ttotalweight=" << m_totalweight_row << std::endl;
	fio << "  row=0 point=" << m_row_point_list[0] << std::endl;
	for(UINT i=0; i<m_rows; i++)
	{
		fio << "  row=" << i << " weight=" << m_row_weight_list[i]
			<< " point=" << m_row_point_list[i+1] << std::endl;
	}
	fio << "  cols\n";
	fio << "\ttotoalweight=" << m_totalweight_col << std::endl;
	for(UINT i=0; i<m_cols; i++)
	{
		fio << "  col=" << i << " weight=" << m_col_weight_list[i]
			<< " point=" << m_col_point_list[i+1] << std::endl;
	}
}

void CStdCtrlSkinTable::DumpShowGrid(std::ofstream& fio, CStdCtrlSkin_EnumDump type)
{
	if ( !((int)type & (int)CStdCtrlSkin_EnumDump::TABLE_SHOW_GRID) )
		return;
	fio << "Show grid info\n";
	fio << "  rows\n";
	fio << "\ttotalweight=" << m_totalweight_row << std::endl;
	fio << "  row=0 point=" << m_show_row_point_list[0] << std::endl;
	for(UINT i=0; i<m_rows; i++)
	{
		fio << "  row=" << i << " weight=" << m_row_weight_list[i]
			<< " point=" << m_show_row_point_list[i+1] << std::endl;
	}
	fio << "  cols\n";
	fio << "\ttotoalweight=" << m_totalweight_col << std::endl;
	for(UINT i=0; i<=m_cols; i++)
	{
		fio << "  col=" << i << " weight=" << m_col_weight_list[i]
			<< " point=" << m_show_col_point_list[i] << std::endl;
	}
}
#endif